﻿/*   
    Copyright (C) 2009 Galaktika Corporation ZAO

    This file is a part of Galaktika.BusinessMonitor
 
    Galaktika.BusinessMonitor is a free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
      
    You should have received a copy of the GNU General Public License
    along with Galaktika.BusinessMonitor.  If not, see <http://www.gnu.org/licenses/>.
  
    If GPL v.3 is not suitable for your products or company,
    Galaktika Corp provides Galaktika.BusinessMonitor under a flexible commercial license
    designed to meet your specific usage and distribution requirements.
    If you have already obtained a commercial license from Galaktika Corp,
    you can use this file under those license terms.
*/

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.IsolatedStorage;
using System.Text;
using System.Runtime.Serialization;
using System.Threading;
using System.Windows;
using System.Windows.Resources;
using Galaktika.BI.Extensibility;
using Galaktika.BI.Extensibility.Dom.Dac;
using Galaktika.BI.Runtime.Services;
using Galaktika.BI.Silverlight.Services;
using Galaktika.BI.Silverlight.ClientApp.Services;
using System.Windows.Threading;
using Galaktika.BI.Extensibility.Dom;

namespace Galaktika.BI.Silverlight.ClientApp
{
    public class ClientAppHolder
    {
        #region AppItem
        private class AppItem
        {
            public AppItem(DateTime lastModified, string content)
            {
                this.LastModified = lastModified;
                this.Content = content;
            }

            public readonly DateTime LastModified;
            public string Content;
            public bool IsChanged;

            public static readonly AppItem Empty = new AppItem(DateTime.MinValue, string.Empty);

            public bool IsEmpty
            {
                get
                {
                    return this.Equals(AppItem.Empty);
                }
            }

            public AppItemDescriptor ToDescriptor(string name)
            {
                return this.ToDescriptor(name, false);
            }

            public AppItemDescriptor ToDescriptor(string name, bool withContent)
            {
                return new AppItemDescriptor() 
                {
                    Name = name,
                    LastModified = this.LastModified,
                    Content = withContent ? this.Content : null
                };
            }
        }
        #endregion
        #region AppCache
        private class AppCache
        {
            #region CacheStorage
            private class CacheStorage
            {
                private static string GetTimeStampName(string itemName)
                {
                    return itemName + ".timestamp";
                }

                private static DateTime ReadItemTimeStamp(IsolatedStorageFile storage, string timestampName)
                {
                    using (var fs = storage.OpenFile(timestampName, FileMode.Open, FileAccess.Read))
                    {
                        using (var sr = new StreamReader(fs, Encoding.UTF8))
                        {
                            var timeStr = sr.ReadToEnd();

                            return DateTime.Parse(timeStr, CultureInfo.InvariantCulture);
                        }
                    }
                }

                private static string ReadItemContent(IsolatedStorageFile storage, string itemName)
                {
                    using (var fs = storage.OpenFile(itemName, FileMode.Open, FileAccess.Read))
                    {
                        using (var sr = new StreamReader(fs, Encoding.UTF8))
                        {
                            return sr.ReadToEnd();
                        }
                    }
                }

                private static void CreateFile(IsolatedStorageFile storage, string itemName, string content)
                {
                    using (var fs = storage.OpenFile(itemName, FileMode.Create, FileAccess.Write))
                    {
                        using (var sw = new StreamWriter(fs, Encoding.UTF8))
                        {
                            sw.Write(content);
                            sw.Flush();
                        }
                    }
                }

                public static AppItem GetItem(string itemName)
                {
                    using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
                    {
                        var timestampName = GetTimeStampName(itemName);

                        if (storage.FileExists(itemName) && storage.FileExists(timestampName))
                        {
                            var lastModified = ReadItemTimeStamp(storage, timestampName);
                            var content = ReadItemContent(storage, itemName);

                            return new AppItem(lastModified, content);
                        }

                        return AppItem.Empty;
                    }
                }

                public static void SetItem(AppItem item, string itemName)
                {
                    using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
                    {
                        var timestampName = GetTimeStampName(itemName);

                        //if (storage.FileExists(timestampName))
                        //{
                        //    storage.DeleteFile(timestampName);
                        //}
                        //if (storage.FileExists(itemName))
                        //{
                        //    storage.DeleteFile(itemName);
                        //}

                        CreateFile(storage, itemName, item.Content);
                        CreateFile(storage, timestampName, item.LastModified.ToString(CultureInfo.InvariantCulture));
                    }
                    item.IsChanged = false;
                }
            }
            #endregion
            #region SettingsMerger
            private class SettingsMerger
            {
                public SettingsMerger(UserSettings initials)
                {
                    m_Settings = initials;
                    this.Merge(initials.Variables);
                }

                private UserSettings m_Settings;
                private Dictionary<string, BrickVariable> m_Vars = new Dictionary<string, BrickVariable>();
                public void Merge(IEnumerable<BrickVariable> initials)
                {
                    foreach (var v in initials)
                    {
                        m_Vars[v.Name] = v;
                    }
                }

                public UserSettings GetSettings()
                {
                    m_Settings.Variables.Clear();
                    m_Settings.Variables.AddRange(m_Vars.Values);
                    return m_Settings;
                }
            }
            #endregion

            //readonly string SettingsXmlItem = "settings.xml";
            readonly string ClientXapItem = "client.xap";
            internal readonly string UserSettingsXmlItem = "userSettings.xml";

            private AppCache()
            {
            }

            private static AppCache m_Instance;
            public static AppCache Instance
            {
                get
                {
                    if (m_Instance == null)
                    {
                        m_Instance = new AppCache();
                    }

                    return m_Instance;
                }
            }

            public bool HasChanges
            {
                get
                {
                    return m_ClientApp.IsChanged || m_UserSettingsItem.IsChanged;
                }
            }

            private AppItem m_ClientApp;
            public AppItem ClientApp
            {
                get
                {
                    return m_ClientApp;
                }
            }

            private AppItem m_UserSettingsItem;
            public AppItem UserSettings
            {
                get
                {
                    return m_UserSettingsItem;
                }
            }

            public void Update(AppItemDescriptor item)
            {
                if (item.Name == ClientXapItem)
                {
                    m_ClientApp = new AppItem(item.LastModified, item.Content) { IsChanged = true };
                }
                if (item.Name == UserSettingsXmlItem)
                {
                    m_UserSettingsItem = new AppItem(item.LastModified, item.Content) { IsChanged = true };
                    m_UserSettings = null;
                }
            }

            public void Save()
            {
                if (m_ClientApp.IsChanged && !m_ClientApp.IsEmpty)
                {
                    CacheStorage.SetItem(m_ClientApp, ClientXapItem);
                }
                if (m_UserSettingsItem.IsChanged && !m_UserSettingsItem.IsEmpty)
                {
                    CacheStorage.SetItem(m_UserSettingsItem, UserSettingsXmlItem);
                }
            }

            public void Load()
            {
                m_ClientApp = CacheStorage.GetItem(ClientXapItem);
                m_UserSettingsItem = CacheStorage.GetItem(UserSettingsXmlItem);
            }

            public IEnumerable<AppItemDescriptor> GetDescriptors()
            {
                return new AppItemDescriptor[] 
                { 
                    m_ClientApp.ToDescriptor(ClientXapItem)
                    //,m_UserSettingsItem.ToDescriptor(UserSettingsXmlItem)
                };
            }

            private UserSettings m_UserSettings;
            public UserSettings GetUserSettings()
            {
                if (m_UserSettings == null)
                {
                    if (!m_UserSettingsItem.IsEmpty)
                    {
                        m_UserSettings = XmlUtility.XmlStr2Obj<UserSettings>(m_UserSettingsItem.Content);
                    }
                }

                return m_UserSettings;
            }

            public void SetUserSettings(UserSettings settings)
            {
                if (m_UserSettings != null)
                {
                    var merger = new SettingsMerger(m_UserSettings);
                    merger.Merge(settings.Variables);

                    m_UserSettings = merger.GetSettings();
                }
                else
                {
                    m_UserSettings = settings;
                    //new UserSettings();
                    //m_UserSettings.Settings = settings.Settings;
                    //m_UserSettings.Variables.AddRange(settings.Variables);
                }

                m_UserSettingsItem = new AppItem(
                    DateTime.Now,
                    XmlUtility.Obj2XmlStr(m_UserSettings, Common.Namespace));
                m_UserSettingsItem.IsChanged = true;
            }
        }
        #endregion

        private class Downloader
        {
            public static IEnumerable<AppItemDescriptor> DownloadItems(IEnumerable<AppItemDescriptor> cachedItems)
            {
                using (var svc = new SyncMetaService())
                {
                    return svc.GetAppItems(new List<AppItemDescriptor>(cachedItems));
                }
            }

            public static void UploadItems(IEnumerable<AppItemDescriptor> items)
            {
                using (var svc = new SyncMetaService())
                {
                    svc.SetAppItems(new List<AppItemDescriptor>(items));
                }
            }
        }

        public static void LoadClientApp(Dispatcher dispatcher)
        {
            AppCache.Instance.Load();
            var cached = AppCache.Instance.GetDescriptors();

            foreach (var item in Downloader.DownloadItems(cached))
            {
                if (!string.IsNullOrEmpty(item.Content))
                {
                    AppCache.Instance.Update(item);
                }
            }

            if (AppCache.Instance.HasChanges)
            {
                dispatcher.BeginInvoke(() =>
                    {
                        var taskMgr = App.CurrentApp.GetService<IUserTaskManager>();
                        if (taskMgr != null)
                        {
                            taskMgr.RunTaskAsync(Localization.AppTask_UpdatingClientCache,
                                _ => AppCache.Instance.Save());
                        }
                    });
            }

            dispatcher.BeginInvoke(() =>
                {
                    ProcessClientXap();
                    ProcessSettings(AppCache.Instance.GetUserSettings());
                    App.CurrentApp.InitializeRoot();
                });
        }

        public static UserSettings GetUserSettins()
        {
            return AppCache.Instance.GetUserSettings();
        }

        public static void SetUserSettings(UserSettings settings)
        {
            AppCache.Instance.SetUserSettings(settings);
            AppCache.Instance.Save();
            ProcessSettings(AppCache.Instance.GetUserSettings());
            //Downloader.UploadItems(new[] { 
            //    AppCache.Instance.UserSettings.ToDescriptor(
            //        AppCache.Instance.UserSettingsXmlItem,
            //        true) 
            //});
        }

        private static void ProcessSettings(UserSettings userSettings)
        {
            //UserSettings userSettings = AppCache.Instance.GetUserSettings();
            if (userSettings != null)
            {
                Settings setting = userSettings.Settings;
                ServiceManagerHelper.Initialize(setting);
                App.CurrentApp.Binder.InitializeSettings(userSettings);

                if (!string.IsNullOrEmpty(setting.CultureName))
                {
                    Thread.CurrentThread.CurrentUICulture = new CultureInfo(setting.CultureName);
                    ((Page)App.CurrentApp.Root).UpdateLocalizableResources();
                }

                if (setting.EnableLogging)
                {
                    OutputManager.Initialize(((Page)App.CurrentApp.Root.RootControl).Output.TextBoxOutput);
                    ((Page)App.CurrentApp.Root.RootControl).ButtonViewOutput.Visibility = Visibility.Visible;
                }
                else
                {
                    OutputManager.Initialize(null);
                    ((Page)App.CurrentApp.Root.RootControl).ButtonViewOutput.Visibility = Visibility.Collapsed;
                }
                if (setting.EnableClientCaching)
                {
                    SyncDataService.EnableCache(App.CurrentApp.Binder.GetStatusHash);
                }
                else
                {
                    SyncDataService.DisableCache();
                }
                App.CurrentApp.Binder.AccessMode = setting.DataAccessMode;
                UserTaskThreadPool.MaxThreads = setting.MaxUserTaskThreads;
            }
            else
            {
                App.CurrentApp.Binder.UserSettings.Settings.BaseUri = Ranet.AgOlap.Services.ServiceManager.BaseAddress;
            }
        }

        private static void ProcessClientXap()
        {
            if (!AppCache.Instance.ClientApp.IsEmpty)
            {
                string mimeType = null;
                byte[] content = Convert.FromBase64String(AppCache.Instance.ClientApp.Content);
                using (var ms = new MemoryStream(content.Length))
                {
                    ms.Write(content, 0, content.Length);

                    // Client.dll
                    //
                    ms.Position = 0;
                    StreamResourceInfo sri = App.GetResourceStream(
                        new StreamResourceInfo(ms, mimeType),
                        new Uri(Galaktika.BI.Silverlight.Consts.ClientDllName, UriKind.Relative));
                    if (sri != null)
                    {
                        try
                        {
                            AssemblyPart asm = new AssemblyPart();
                            ClientResources.Initialize(asm.Load(sri.Stream));
                        }
                        catch (Exception exc)
                        {
                            App.CurrentApp.LogError(exc);
                        }
                    }

                    // dal.xml
                    //
                    ms.Position = 0;
                    sri = App.GetResourceStream(
                        new StreamResourceInfo(ms, mimeType),
                        new Uri(Galaktika.BI.Silverlight.Consts.DALContainerName, UriKind.Relative));
                    if (sri != null)
                    {
                        try
                        {
                            BrickDacContainer container = BrickDacContainer.FromStream(sri.Stream);
                            App.CurrentApp.Binder.InitializeDac(container);
                        }
                        catch (Exception exc)
                        {
                            App.CurrentApp.LogError(exc);
                        }
                    }

                    // metadata.xml
                    //
                    ms.Position = 0;
                    sri = App.GetResourceStream(
                        new StreamResourceInfo(ms, mimeType),
                        new Uri(Galaktika.BI.Silverlight.Consts.MetadataContainerName, UriKind.Relative));
                    if (sri != null)
                    {
                        try
                        {
                            MetaContainer container = MetaContainer.FromStream(sri.Stream);
                            App.CurrentApp.Initialize(container);
                        }
                        catch (Exception exc)
                        {
                            App.CurrentApp.LogError(exc);
                        }
                    }
                }
            }
        }
    }
}